#include "run_functions.h"
#include "optlist/optlist.h"

/* Parse command line arguments using the optlist library and populate an 
   ArgStruct.  Do not perform any arg checking; this is to be done in 
   the function checkArgs() 

   This function makes use of the ANSI optlist library, obtained under the 
   GNU LGPL from http://michael.dipperstein.com/optlist/index.html.  Thanks to
   Michael Dipperstein for his excellent library.*/
ArgStruct * parseArgs(int argc, char * argv[]) {

    ArgStruct *args;
    option_t *optList, *thisOpt;

    // Initialize the ArgStruct;
    args = (ArgStruct*)malloc(sizeof(ArgStruct));
    
    args->help         = FALSE;
    args->verbose      = FALSE;
    args->test         = FALSE;
    args->m            = -1;
    args->t            = -1;
    args->num_DEs      = -1;
    args->decoder_mode = MODE_SERIAL;
    args->ifile        = NULL;
    args->dfile        = NULL;
    args->ofile        = NULL;
    args->gen_data     = FALSE;
    args->no_run       = FALSE;
    args->data_length  = -1;
    args->e_prob       = -1;
    args->e_per_word   = -1;
    args->e_pmf        = NULL;
        

    optList = NULL;
    optList = GetOptList(argc, argv, "hvTm:t:D:i:f:o:gNl:p:e:d:M:");

    /* Parse */
    while (optList != NULL) {
        thisOpt = optList;
        optList = optList->next;

        switch(thisOpt->option) {

            case 'h': {
                args->help = TRUE;
                break;
            }

            case 'v': {
                args->verbose = TRUE;
                break;
            }

            case 'T': {
                args->test = TRUE;
                break;
            }           
 
            case 'm': {
                args->m = atoi(thisOpt->argument); // Returns 0 if invalid
                break;
            }

            case 't': {
                args->t = atoi(thisOpt->argument); // Returns 0 if invalid
                break;
            }

            case 'D': {
                args->num_DEs = atoi(thisOpt->argument); // Returns 0 if invalid
                break;
            }

            case 'i': {
                args->ifile = thisOpt->argument;
                break;
            }

            case 'f': {
                args->dfile = thisOpt->argument;
                break;
            }

            case 'o': {
                args->ofile = thisOpt->argument;
                break;
            }

            case 'g': {
                args->gen_data = TRUE;
                break;
            }

            case 'N': {
                args->no_run = TRUE;
                break;
            }

            case 'l': {
                args->data_length = atoi(thisOpt->argument); // Returns 0 if invalid
                break;
            }


            case 'p': {
                args->e_prob = atof(thisOpt->argument); // Returns 0.0 if invalid
                break;
            }

            case 'e': {
                args->e_per_word = atoi(thisOpt->argument); // Returns 0 if invalid
                break;
            }

            case 'd': {
                args->e_pmf = thisOpt->argument;
                break;
            }

            case 'M': {
                args->decoder_mode = atoi(thisOpt->argument);
                break;
            }

            default: {
                // Invalid arg detected: need to print an error and exit.
                // TODO: print error and exit
                FreeOptList(thisOpt);
                break;
            }
            free(thisOpt);  // Done with this item, now free it
        }    

    }
 

    return args;

}

//////////////////////////////////////////////////////////////////////////////
/* Check all values populated into the input ArgStruct and confirm that they
   are legal values for their respective variables.  Report to stderr and exit 
   if any invalid values or other errors are encountered. */
//////////////////////////////////////////////////////////////////////////////
int checkArgs(ArgStruct * args) {

    /* We need to check the following cases:
        1. args->help == TRUE: return success
        2. m != -1: m >= 1, m < 9
        3. t = floor((n-k)/2) -> 0 < 2t <= 2^(m-1) - 1 (k must be at least 1)
        4. num_DEs must be either -1 or a positive integer
        5. If ifile is not NULL, make sure that none of the generate data 
           flags are passed
        6. If ofile is not NULL, make sure gen_data is flagged, along with 
           data_length and either e_prob or {e_per_word and e_pmf}
        7. If no_run is flagged and m, t, and/or num_DEs are also flagged, 
           issue a warning that the given run parameters won't be used.
        8. If any of the gen_data parameters are passed and gen_data is not 
           flagged, return failure. 
    */

    if (args->help == TRUE) {
        return ARG_SUCCESS;  // No need to check other args because we're just
                             // going to return the help message. 
    }

    if (args->no_run == TRUE) { // If no_run is flagged and m, t, and/or num_DEs are also flagged, 
                          // issue an error because the given run parameters won't be used.
        //if (args->m != -1 || args->t != -1 || args->num_DEs != -1) {
        //    fprintf(stderr, "Error: if no_run is enabled, no run paramaters may be supplied.\n");
        //    return ARG_FAILURE;
        //}

    }

    if (args->m != -1) { // m has been specified, so check for a legal value
        if  (args->m < 1 || args->m > 8) {
            fprintf(stderr, "Error: input value m must be an integer in the range [1 .. 8].\n");
            return ARG_FAILURE;
        }
    }
    else {
        args->m = DEFAULT_M;
    }
    
    if (args->t != -1) { // t has been specified, so check for a legal value
        if (args->t < 1 || (2*(args->t) > ((1 << (args->m)) - 1))) {
            fprintf(stderr, "Error: input value t must be a positive integer s.t. 2t <= 2^(m-1) - 1.\n");
            return ARG_FAILURE;
        }
    }
    else {
        args->t = DEFAULT_T;
    }

    if (args->num_DEs != -1) { // num_DEs has been specified, so check for a legal value
        if (args->num_DEs < 1 || args->num_DEs >= 1024) {
            fprintf(stderr, "Error: input value num_DEs must be a positive integer no greater than 1024.\n");
            return ARG_FAILURE;
        }
    }

    if (args->ifile != NULL) { // ifile has been specified, so check to make sure 
                              // that no data generation flags have been passed
        if (args->gen_data == TRUE  || 
            args->ofile != NULL     || 
            args->no_run == TRUE    ||
            args->data_length != -1 ||
            args->e_prob != -1      ||
            args->e_per_word != -1  ||
            args->e_pmf != NULL
            ) {
            
            fprintf(stderr, "Error: if an input file is specified, no data generation flags may be passed.\n");
            return ARG_FAILURE;
        }
    }

    if (args->ofile != NULL) { // If ofile is not NULL, make sure gen_data is flagged,
                               // along with data_length and either e_prob or {e_per_word and e_pmf}
        if (args->gen_data == FALSE || 
            args->data_length == -1 || 
            (args->e_prob == -1 && 
                (args->e_per_word == -1 || args->e_pmf == NULL))) {

            fprintf(stderr, "Error: if an output file is specified, file generation parameters must be supplied.\n");
            return ARG_FAILURE;

        }

    }

    if (!args->no_run && args->dfile == NULL) {
        fprintf(stderr, "Error: a file must be specified for the output data.\n");
        return ARG_FAILURE;
    }

    if (args->gen_data == TRUE) {

        if (args->data_length == -1 ||
            (args->e_prob == -1 &&
                (args->e_per_word == -1 || args->e_pmf == NULL))) {

            fprintf(stderr, "Error: if g is flagged, generation parameters must be supplied.\n");
            return ARG_FAILURE;

        }

    }

    if (args->gen_data == FALSE) { // If any of the gen_data parameters are passed and gen_data is not 
                                   // flagged, return failure. 
        if (args->gen_data == TRUE  ||
            args->ofile != NULL     || 
            args->no_run == TRUE    ||
            args->data_length != -1 ||
            args->e_prob != -1      ||
            args->e_per_word != -1  ||
            args->e_pmf != NULL
            ) {

            fprintf(stderr, "Error: if gen_data is not set, no data generation parameters may be passed.\n");
            return ARG_FAILURE;   
        }
    } 

    return ARG_SUCCESS;

}

// Parse values out a character array to populate an array for e_pmf
// Need to check that there are the proper number of entries, and that the sum 
// of the entries is 1.
double * parse_pmf(char *e_pmf, int m, int t) {

    double *pmf;
    char *pch, *pch_prev;
    int i;
    int comma_count;
    int pmf_sum;

    // First, check the number of commas to make sure that there are the right 
    // number of entries.  Also check for invalid characters.
    comma_count = 0;
    i = 0;
    while (e_pmf[i] != '\0') {
        
        // Look to see if the character is a comma
        if (e_pmf[i] == ',') comma_count++;
        else {

            // Check for invalid characters
            if (e_pmf[i] != '.' && (e_pmf[i] < '0' || e_pmf[i] > '9')) {
                fprintf(stderr, "Error: Invalid character '%c' in error pmf.  See help files for syntax information.\n", e_pmf[i]);
                return NULL;
            }
        }
        
        i++;
    }

    // Check to make sure that there are the correct number of commas
    if (comma_count != (1<<m)-2) {
        fprintf(stderr, "Error: The number of specified error probabilities must equal the code word length, which is 2^m - 1.  See help files for more information.\n");
        return NULL;
    }

    // Read the values out of the string
    pmf = (double *)malloc((comma_count+1)*sizeof(double));

    i = 0;
    pch_prev = e_pmf; 
    pch = strchr(e_pmf, ',');
    while(pch != NULL) {
        pmf[i] = atof(pch_prev);
        pch_prev = pch+1;
        pch = strchr(pch+1, ',');
        i++;
    }
    pmf[i] = atof(pch_prev);

    // Check to make sure the pmf sums to 1
    pmf_sum = 0;
    for (i = 0; i < comma_count + 1; i++) {
        pmf_sum += pmf[i];
    }

    if (pmf_sum != 1) {
        fprintf(stderr, "Error: the error probabilities must sum to 1 in order to create a valid pmf.  See help files for more information.\n");
        return NULL;
    }

    return pmf;

}

